-module(bmula_smtpc).
-include("bmula.inc").

-include_lib("kernel/src/inet_dns.hrl").

-export([deliver/3, deliver/4]).

connect({Host, Port}) ->
	{ok, Socket} = gen_tcp:connect(Host, Port, [{packet, raw}, {reuseaddr, true}, {active, false}, binary]),
	Socket.
connection_receive(Socket) ->
	{ok, Data} = gen_tcp:recv(Socket, 0),
	Data.
connection_send(Socket, Message) ->
	gen_tcp:send(Socket, Message ++ "\r\n").
disconnect(Socket) ->
	gen_tcp:close(Socket).

receive_to_end(C, Message) ->
	Size = size(Message) - 2,
	case Message of
		<<_:Size/binary, $\r, $\n>> -> done;
		_ -> receive_to_end(C, connection_receive(C))
	end.

recv(C) ->
	<<Code:3/binary, Message/binary>> = connection_receive(C),
	receive_to_end(C, Message),
	list_to_integer(binary_to_list(Code)).

send(C, Message) ->
	connection_send(C, Message).

send_recv(C, Message) ->
	send(C, Message),
	recv(C).

script(XM, To, Mail) ->
	try
		C = connect(XM),
		try
			220 = recv(C),
			250 = send_recv(C, [<<"HELO 3spire.com">>]),
			250 = send_recv(C, [<<"MAIL FROM:<system@3spire.com>">>]),
			250 = send_recv(C, [<<"RCPT TO:<">>, To, <<">">>]),
			354 = send_recv(C, [<<"DATA">>]),
			250 = send_recv(C, [Mail]),
			221 = send_recv(C, [<<"QUIT">>]),
			?STAT(smtpc, successful, 1),
			true
		catch
			SX:SY ->
				?WARNING("smtpc:script(~p, ~p, ...) - failed to send - ~p:~p~n", [XM, To, SX, SY]),
				send(C, "QUIT"),
				false
		after
			disconnect(C)
		end
	catch
		CX:CY ->
			?WARNING("smtpc:script(~p, ~p, ...) - failed to connect - ~p:~p~n", [XM, To, CX, CY]),
			false
	end.

escape(Message) -> escape(Message, 0).
escape(Message, Offset) when size(Message) > Offset ->
	case Message of
		<<Pre:Offset/binary, $., Post/binary>> ->
			escape(<<Pre/binary, $., $., Post/binary>>, Offset + 2);
		_ ->
			escape(Message, Offset + 1)
	end;
escape(Message, _) -> Message.

deliver_loop(Self, To, _, _, 0) ->
	?WARNING("smtpc:deliver_loop(~p, ~p, ..., ..., 0) - gave up sending~n", [Self, To]),
	?STAT(smtpc, failed, 1),
	Self ! {self(), failed};
deliver_loop(Self, To, Mail, XMs, Trys) ->
	[XM | NewXMs] = case XMs of
		[] ->
			{_Local, Domain} = bmula_rfc2822:'addr-spec'(To),
			try
				{ok, #dns_rec{anlist = AnList}} = inet_res:nslookup(binary_to_list(Domain), 1, mx),
				mapfilter(fun
				(#dns_rr{type = ?S_MX, data = {_, D}}) ->
					{D, 25}
				end, AnList)
			catch
				DX:DY ->
					?WARNING("smtpc:deliver_loop/5 To=~p - failed nslookup - ~p:~p~n", [To, DX, DY]),
					[{Domain, 25}]
			end;
		X -> X
	end,
	case script(XM, To, Mail) of
		true ->
			Self ! {self(), ok};
		false ->
			?STAT(smtpc, trys, 1),
			receive
			after 350000 ->
				deliver_loop(Self, To, Mail, NewXMs, Trys - 1)
			end
	end.

deliver(To, Subject, Message) -> deliver(To, Subject, Message, false).
deliver(To, Subject, Message, Async) ->
	Self = self(),
	Pid = spawn(fun() ->
		Mail = escape(list_to_binary([<<"Subject:">>, Subject, <<"\r\nFrom:system@3spire.com\r\n\r\n">>, Message])),
		deliver_loop(Self, list_to_binary([To]), <<Mail/binary, "\r\n.">>, [], 100)
	end),
	if
		Async -> receive {Pid, News} -> News end;
		true  -> ok
	end.

mapfilter(F, List) ->
	Loop = fun
	([], Acc, _) -> lists:reverse(Acc);
	([Item | Items], Acc, L) ->
		try
			L(Items, [F(Item) | Acc], L)
		catch _:_ ->
			L(Items, Acc, L)
		end
	end,
	Loop(List, [], Loop).

